package com.ds.web.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.ds.annotation.Aggregation;
import com.ds.annotation.RequestType;
import com.ds.common.JDSException;
import com.ds.common.util.ClassUtility;
import com.ds.common.util.StringUtility;
import com.ds.config.JDSConfig;
import com.ds.config.ListResultModel;
import com.ds.config.ResultModel;
import com.ds.config.TreeListResultModel;
import com.ds.context.JDSActionContext;
import com.ds.esd.annotation.ui.ComponentType;
import com.ds.esd.annotation.view.DynLoadAnnotation;
import com.ds.esd.bean.MethodConfig;
import com.ds.esd.bean.data.CustomDynDataBean;
import com.ds.esd.bean.view.CustomModuleBean;
import com.ds.esd.custom.CustomViewFactory;
import com.ds.esd.custom.DataComponent;
import com.ds.esd.engine.ESDFacrory;
import com.ds.esd.engine.EUModule;
import com.ds.esd.engine.ProjectVersion;
import com.ds.esd.engine.enums.PackagePathType;
import com.ds.esd.tool.component.APICallerComponent;
import com.ds.esd.tool.component.ModuleComponent;
import com.ds.esd.tool.component.PageBarComponent;
import com.ds.jds.core.esb.EsbUtil;
import com.ds.server.httpproxy.core.Request;
import com.ds.server.httpproxy.core.Response;
import com.ds.template.JDSFreemarkerResult;
import com.ds.web.APIConfigFactory;
import com.ds.web.BaseParamsEnums;
import com.ds.web.RequestMethodBean;
import com.ds.web.RequestParamBean;
import com.ds.web.util.AnnotationUtil;
import com.ds.web.util.JSONGenUtil;
import com.ds.web.util.MethodUtil;
import freemarker.template.TemplateException;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class BaseInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(BaseInterceptor.class);

    public static final String CUSSCLASSNAME = "_currClassName_";

    public static final String ProjectName = "projectName";

    public static final String ProjectVersionName = "projectVersionName";

    public static final String VVVERSION = "VVVERSION";

    public static final String[] CKEY = new String[]{"preview", "public", "custom", "debug", "RAD", "root"};


    protected String getCustomClassName(HttpServletRequest request) {
        String euClassName = request.getParameter("tag");
        if (euClassName == null) {
            MethodConfig methodConfig = getMethodConfig(request);
            euClassName = methodConfig.getEUClassName();
        }
        JDSActionContext.getActionContext().getContext().put("className", euClassName);
        return euClassName;
    }

    protected RequestMethodBean findMethodBean(String path) {
        RequestMethodBean methodBean = APIConfigFactory.getInstance().getRequestMappingBean(path);
        if (methodBean == null) {
            methodBean = APIConfigFactory.getInstance().findMethodBean(path);
        }
        return methodBean;
    }


    public boolean sendJSON(HttpServletResponse response, String json) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.addHeader("content-type", "application/javascript");
        response.getWriter().write(json.toString());

        return true;
    }


    public boolean sendFtl(HttpServletResponse response, String resource) throws IOException {
        String responseStr = "";
        try {
            JDSFreemarkerResult result = new JDSFreemarkerResult();
            StringWriter stringWriter = (StringWriter) result.doExecute(resource, null);
            responseStr = stringWriter.toString();
        } catch (TemplateException e) {
            e.printStackTrace();
        }
        response.setCharacterEncoding("UTF-8");
        response.addHeader("content-type", "text/html");
        response.getWriter().write(responseStr);
        return true;
    }


    public MethodConfig getMethodConfig(HttpServletRequest request) {
        MethodConfig methodConfig = null;
        try {
            methodConfig = CustomViewFactory.getInstance().getMethodAPIBean(request.getRequestURI(), this.getProjectName(request));
        } catch (JDSException e) {
            e.printStackTrace();
        }
        return methodConfig;
    }

    public Object invokMethod(RequestMethodBean methodBean, HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, OgnlException {
        Object object = null;
        Object service = this.getService(methodBean, request);
        if (service == null) {
            return null;
        }

        RequestMethodBean realMethodBean = null;
        if (!service.getClass().getName().equals(methodBean.getSourceMethod().getDeclaringClass().getName())) {
            realMethodBean = new RequestMethodBean(MethodUtil.getEqualMethod(service.getClass(), methodBean.getSourceMethod()), methodBean.getMappingBean(), methodBean.getDomainId());
        }


        Map<String, Object> allParamsMap = this.getAllParamMap(methodBean, request);
        JDSActionContext.getActionContext().getContext().put(CustomViewFactory.CurrModuleKey, getEUModule(methodBean, request, allParamsMap));
        Map<String, String> paramsMap = methodBean.getParamsMap();
        Set<RequestParamBean> keySet = methodBean.getParamSet();
        List<Object> objects = new ArrayList<>();
        List<Class> objectTyps = new ArrayList<>();

        DynLoadAnnotation dynAnnotation = AnnotationUtil.getMethodAnnotation(methodBean.getSourceMethod(), DynLoadAnnotation.class);
        ModuleComponent moduleComponent = null;
        for (RequestParamBean paramBean : keySet) {
            Class paramClass = paramBean.getParamClass();
            if (realMethodBean != null) {
                paramClass = realMethodBean.getRealParams(paramBean.getParamName()).getParamClass();
            }

            Class ctClass = ClassUtility.loadClass(paramsMap.get(paramBean.getParamName()));
            String iClassName = ctClass.getName();
            Class iClass = ClassUtility.loadClass(iClassName);
            String key = paramBean.getParamName();
            Object value = null;
            try {
                RequestBody requestBody = null;
                for (Annotation annotation : paramBean.getAnnotations()) {
                    if (annotation.annotationType().equals(RequestBody.class)) {
                        requestBody = (RequestBody) annotation;
                    }
                }
                if (requestBody != null) {
                    if (methodBean.getRequestType().equals(RequestType.JSON)) {
                        for (BaseParamsEnums baseParams : BaseParamsEnums.values()) {
                            if (request.getParameterMap().containsKey(baseParams.name())) {
                                JDSActionContext.getActionContext().getContext().put(baseParams.name(), request.getParameterMap().get(baseParams.name()));
                            }
                        }
                        String json = request.getReader().lines().collect(Collectors.joining());
                        value = JSONObject.parseObject(json, paramClass);
                    }
                } else if (Request.class.isAssignableFrom(iClass)) {
                    value = request;
                } else if (ModuleComponent.class.isAssignableFrom(iClass)) {
                    if (moduleComponent == null) {
                        moduleComponent = getEUModule(methodBean, request, allParamsMap).getComponent().getRealModuleComponent();
                    }
                    value = moduleComponent;
                } else if (EUModule.class.isAssignableFrom(iClass)) {
                    value = getEUModule(methodBean, request, allParamsMap);

                } else if (Response.class.isAssignableFrom(iClass)) {
                    value = response;
                } else {
                    switch (methodBean.getRequestType()) {
                        case FORM:
                            if (iClass.isArray()) {
                                if (JSONObject.parseArray(allParamsMap.get(key).toString()).size() > 0) {
                                    value = JSONObject.parseArray(allParamsMap.get(key).toString(), iClass.getComponentType()).toArray();
                                } else {
                                    value = Array.newInstance(iClass.getComponentType(), 0);
                                }
                            } else {
                                if (paramBean.getJsonData()) {
                                    if (iClass.isArray() || Collection.class.isAssignableFrom(iClass)) {
                                        value = JSONArray.parseObject(allParamsMap.get(key).toString(), paramBean.getParamType());
                                    } else {
                                        value = JSONObject.parseObject(allParamsMap.get(key).toString(), paramBean.getParamType());
                                    }

                                } else {
                                    value = TypeUtils.castToJavaBean(allParamsMap.get(key), iClass);
                                }
                            }
                            break;
                        case JSON:
                            String json = request.getReader().lines().collect(Collectors.joining());
                            JSONObject jsonObject = JSON.parseObject(json);
                            if (jsonObject != null) {
                                for (BaseParamsEnums baseParams : BaseParamsEnums.values()) {
                                    if (jsonObject.containsKey(baseParams.name())) {
                                        JDSActionContext.getActionContext().getContext().put(baseParams.name(), jsonObject.get(baseParams.name()));
                                    }
                                }

                                String paramName = paramBean.getParamName();
                                Object obj = jsonObject.get(paramName);
                                if (obj != null) {
                                    value = TypeUtils.castToJavaBean(obj, iClass);
                                    JDSActionContext.getActionContext().getContext().put(paramName, value);
                                }

                            }
                            break;

                        default:
                            value = TypeUtils.cast(allParamsMap.get(paramBean.getParamName()), iClass, null);
                            break;
                    }

                }
            } catch (Throwable e) {
                e.printStackTrace();
                logger.error("参数转换错误：  [" + paramBean.getParamName() + "] convertValue err " + e.getMessage());
            }
            objectTyps.add(iClass);
            objects.add(value);
        }

        if (service != null) {
            if (dynAnnotation != null) {
                object = OgnlRuntime.callMethod(JDSActionContext.getActionContext().getOgnlContext(), service, methodBean.getMethodName(), objects.toArray());
                Map ctx = ((ResultModel) object).getCtx();
                if (ctx != null) {
                    allParamsMap.putAll(ctx);
                }

                if (JSONGenUtil.getInnerReturnType(methodBean).isAssignableFrom(EUModule.class)) {
                    EUModule otherModule = ((ResultModel<EUModule>) object).getData();
                    try {
                        ((ResultModel) object).setData(otherModule.getRealComponents(true));
                    } catch (JDSException e) {
                        e.printStackTrace();
                    }
                } else if (!JSONGenUtil.getInnerReturnType(methodBean).isAssignableFrom(com.ds.esd.tool.component.Component.class) || !dynAnnotation.refClassName().equals("")) {
                    if (moduleComponent == null) {
                        try {
                            moduleComponent = getEUModule(methodBean, request, allParamsMap).getComponent().getRealModuleComponent().clone();
                        } catch (JDSException e) {
                            e.printStackTrace();
                        }
                    }

                    if (moduleComponent != null) {
                        ResultModel resultModel = (ResultModel) object;
                        boolean syncLoad = false;
                        if (moduleComponent instanceof DataComponent) {
                            List<APICallerComponent> componentList = moduleComponent.findComponents(ComponentType.APICALLER, "Reload");
                            for (APICallerComponent api : componentList) {
                                if (api.getAlias().equals("Reload") && api.getProperties().getAutoRun()) {
                                    syncLoad = true;
                                    // api.getProperties().setAutoRun(false);
                                }
                            }
                            if (syncLoad) {
                                ((DataComponent) moduleComponent).setData(resultModel.getData());
                                if (resultModel instanceof TreeListResultModel) {
                                } else if (resultModel instanceof ListResultModel) {
                                    ListResultModel listResultModel = (ListResultModel) object;
                                    List<PageBarComponent> pageBarComponents = moduleComponent.findComponents(ComponentType.PAGEBAR, null);
                                    if (pageBarComponents.size() > 0) {
                                        pageBarComponents.get(0).setData(Integer.valueOf(listResultModel.getSize()));
                                    }
                                }
                            }
                        }
                    }

                    ((ResultModel) object).setData(moduleComponent.getTopComponents(true));
                }
            } else {
                object = OgnlRuntime.callMethod(JDSActionContext.getActionContext().getOgnlContext(), service, methodBean.getMethodName(), objects.toArray());
            }
        } else {
            object = OgnlRuntime.callMethod(JDSActionContext.getActionContext().getOgnlContext(), service, methodBean.getMethodName(), objects.toArray());
        }

        return object;
    }

    public Object getService(RequestMethodBean methodBean, HttpServletRequest request) throws ClassNotFoundException, OgnlException {
        Map<String, Object> allParamsMap = this.getAllParamMap(methodBean, request);
        Class clazz = ClassUtility.loadClass(methodBean.getClassName());
        Object service = getRealService(clazz);
        for (Field field : clazz.getDeclaredFields()) {
            if (allParamsMap.get(field.getName()) != null) {
                try {
                    OgnlRuntime.setProperty(JDSActionContext.getActionContext().getOgnlContext(), service, field.getName(), TypeUtils.castToJavaBean(allParamsMap.get(field.getName()), field.getType()));
                } catch (OgnlException e) {
                }
            }
        }
        return service;
    }

    public Map<String, Object> getAllParamMap(RequestMethodBean methodBean, HttpServletRequest request) {
        Map<String, Object> contextMap = new HashMap();
        Map<String, String[]> paramsMap = request.getParameterMap();
        Set<String> keySet = paramsMap.keySet();
        for (String key : keySet) {
            String[] obj = paramsMap.get(key);
            if (obj != null && obj.length == 1) {
                contextMap.put(key, obj[0]);
            } else {
                contextMap.put(key, obj);
            }
        }
        return contextMap;
    }

    public String formatPath(String urlStr, String projectName) throws MalformedURLException {
        String path = urlStr;
        if (urlStr.startsWith("http")) {
            URL url = new URL(urlStr);
            path = url.getPath();
        }
        if (path.startsWith("/")) {
            path = path.substring(1, path.length());
        }
        for (String ckey : CKEY) {
            String key = ckey + "/";
            if (path.startsWith(key)) {
                path = path.substring(key.length());
            }
        }
        if (projectName != null) {
            if (projectName.indexOf(VVVERSION) > -1) {
                projectName = projectName.split(VVVERSION)[0];
            }
            if ((path.startsWith(projectName + "/") || path.startsWith(projectName + VVVERSION))) {
                path = path.substring(path.indexOf("/") + 1);
            }
        }


        return path;
    }

    Object getRealService(Class clazz) throws OgnlException {
        Object service = null;
        if (clazz.getInterfaces().length > 0) {
            service = EsbUtil.parExpression(clazz.getInterfaces()[0]);
        } else {
            service = EsbUtil.parExpression(clazz);
        }

        if (service == null) {
            if (clazz.isInterface()) {
                Aggregation aggregation = (Aggregation) clazz.getAnnotation(Aggregation.class);
                if (aggregation != null && !aggregation.rootClass().equals(Void.class) && !aggregation.rootClass().equals(clazz)) {
                    clazz = aggregation.rootClass();
                    service = getRealService(clazz);
                }
            } else {
                service = OgnlRuntime.callConstructor(JDSActionContext.getActionContext().getOgnlContext(), clazz.getName(), new Object[]{});
            }

        }

        return service;
    }

    public EUModule getEUModule(RequestMethodBean methodBean, HttpServletRequest request, Map<String, Object> allParamsMap) {
        EUModule module = null;
        try {
            MethodConfig methodAPIBean = ESDFacrory.getAdminESDClient().getMethodAPIBean(methodBean.getUrl(), getProjectName(request));
            CustomDynDataBean customDynDataBean = methodAPIBean.getDynDataBean();
            CustomModuleBean moduleAnnotation = methodAPIBean.getModuleBean();
            Object obj = allParamsMap.get(CUSSCLASSNAME);
            if (customDynDataBean != null && moduleAnnotation != null) {
                PackagePathType packagePathType = PackagePathType.startPath(StringUtility.replace(customDynDataBean.getRefClassName(), ".", "/"));

                if (!customDynDataBean.getRefClassName().equals("")) {
                    String projectName = customDynDataBean.getProjectName();
                    if (projectName == null || projectName.equals("")) {
                        if (packagePathType != null) {
                            packagePathType.getApiType().getDefaultProjectName();
                        }
                    }
                    ProjectVersion projectVersion = ESDFacrory.getAdminESDClient().getProjectVersionByName(projectName);
                    module = projectVersion.getModule(customDynDataBean.getRefClassName());
                    if (module == null) {
                        module = ESDFacrory.getAdminESDClient().rebuildCustomModule(customDynDataBean.getRefClassName(), projectName, allParamsMap);
                    }
                    if (module != null) {
                        module.getComponent().getRealModuleComponent().fillFormValues(allParamsMap, true);
                    }
                } else {
                    String projectName = this.getProjectName(request);
                    Class componentClass = JSONGenUtil.getInnerReturnType(methodAPIBean.getMethod());
                    if (ModuleComponent.class.isAssignableFrom(componentClass)) {
                        module = ESDFacrory.getAdminESDClient().buildDynCustomModule(componentClass, allParamsMap, true);
                    } else {
                        String currClassName = methodAPIBean.getEUClassName();
                        packagePathType = PackagePathType.startPath(StringUtility.replace(currClassName, ".", "/"));
                        if (packagePathType != null && packagePathType.getApiType() != null && packagePathType.getApiType().getDefaultProjectName() != null) {
                            module = CustomViewFactory.getInstance().createRealView(methodAPIBean, null, packagePathType.getApiType().getDefaultProjectName(), allParamsMap, true);
                        } else {
                            module = CustomViewFactory.getInstance().createRealView(methodAPIBean, null, projectName, allParamsMap, true);
                        }
                    }
                }
            } else {
                String projectName = this.getProjectName(request);
                String currClassName = obj.toString();
                module = ESDFacrory.getAdminESDClient().getModule(currClassName, projectName);
                if (module != null) {
                    module.getComponent().getRealModuleComponent().fillFormValues(allParamsMap, true);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return module;
    }


    public String getProjectName(HttpServletRequest request) {
        String projectName = request.getParameter(ProjectVersionName);
        if (projectName == null || projectName.equals("") || (projectName.equals("projectManager") && request.getParameter(ProjectName) != null)) {
            projectName = request.getParameter(ProjectName);
        }
        if (projectName == null || projectName.equals("")) {
            projectName = JDSConfig.getValue("projectName");
        }
        JDSActionContext.getActionContext().getContext().put("projectName", projectName);
        return projectName;
    }

}

